home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / greasemonkey-0.8.20080609.0-fx.xpi / chrome / greasemonkey.jar / chromeFiles / content / updater.js < prev    next >
Text File  |  2008-06-09  |  10KB  |  318 lines

  1. /**
  2.  * Greasemonkey autoupdater.
  3.  *
  4.  * This will run in the background silently, if an update is installed,
  5.  * users will see the standard '[extensionname] has been updated,
  6.  * restart to ...' in the extension manager.
  7.  *
  8.  * ExtensionUpdater will look for the em:updateURL of the selected
  9.  * extension in the extension manager RDF, if found it will compare
  10.  * the version number of the installed extension against the version
  11.  * number listed in the RDF at the linked updateURL. If the local
  12.  * version is older, ExtensionUpdater will download and install the
  13.  * update. This process is entirely silent, and the only way a user
  14.  * can tell that their extension has been updated would be to look for
  15.  * '[extensionname] will be upgraded when firefox is restarted' in
  16.  * their extension manager. (On that note, no matter where in the
  17.  * startup sequence you call this object, Firefox will still require
  18.  * a restart after the extension is updated).
  19.  */
  20. var AUTOUPDATE_DEFAULT_VALUE = true;
  21.  
  22. /**
  23.  * @param {String} id ID of extension (as used in install.rdf)
  24.  * @constructor
  25.  */
  26. function ExtensionUpdater(id) {
  27.   if (!id) {
  28.     throw new Error("You need to specify an extension id");
  29.   }
  30.  
  31.   this.RDFSvc_  = Cc["@mozilla.org/rdf/rdf-service;1"]
  32.                     .getService(Ci.nsIRDFService);
  33.  
  34.   this.id = id;
  35.   this.updating = false;
  36. }
  37.  
  38. /**
  39.  * Begins the update process.
  40.  *
  41.  * Designed so that Update can be called multiple times (e.g at set
  42.  * intervals). Also silently returns if an update is in progress.
  43.  */
  44. ExtensionUpdater.prototype.update = function() {
  45.   if (this.updating) {
  46.     return false;
  47.   }
  48.  
  49.   this.updating = true;
  50.  
  51.   this.appVersion = "";
  52.   this.appID = "";
  53.  
  54.   this.currentVersion = "";
  55.   this.updateURL = "";
  56.  
  57.   this.updateLink = "";
  58.   this.updateVersion = "";
  59.  
  60.   if (!this.getCurrent()) {
  61.     GM_log("ExtensionUpdater: Could not find " + this.id + " in " +
  62.            "extension manager");
  63.     return false;
  64.   }
  65.  
  66.   this.attemptUpdate();
  67. }
  68.  
  69. /**
  70.  * For now, just update every 24 hours. We check a preference to see
  71.  * when we last updated, and then hook a timer to update 24 hours from now.
  72.  * Note that it is still possible, if unlikely, to have concurrent update
  73.  * requests unless the updater is created in a global context.
  74.  *
  75.  * @param {Boolean} if false, do not create the timer
  76.  */
  77. ExtensionUpdater.prototype.updatePeriodically = function() {
  78.   var lastUpdate = GM_prefRoot.getValue("lastUpdate", 0);
  79.   var timeBetweenUpdates = 24 * 60 * 60 * 1000;
  80.   // Hopefully people don't screw with the value
  81.   var nextUpdate = Number(lastUpdate) + timeBetweenUpdates;
  82.   var now = new Date().getTime();
  83.   GM_log("ExtensionUpdater: Last update: " + lastUpdate + ", " +
  84.          "next: " + nextUpdate);
  85.   if (now > nextUpdate) {
  86.     GM_prefRoot.setValue("lastUpdate", String(now));
  87.     this.update();
  88.     nextUpdate = now + timeBetweenUpdates;
  89.   }
  90.  
  91.   GM_log("ExtensionUpdater: Setting timer for update in " +
  92.          (nextUpdate - now) + "ms");
  93.   this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  94.   this.timer_.init(this, nextUpdate - now, this.timer_.TYPE_ONE_SHOT);
  95. }
  96.  
  97. ExtensionUpdater.prototype.observe = function(subject, topic, data) {
  98.   if (topic != "timer-callback") {
  99.     GM_log("ExtensionUpdater: unexpected observer topic: " + topic);
  100.     return;
  101.   }
  102.  
  103.   this.update();
  104. };
  105.  
  106. /**
  107.  * Goes through local extension manager RDF and finds the relevant details
  108.  * for the selected extension.
  109.  */
  110. ExtensionUpdater.prototype.getCurrent = function() {
  111.   var updItem = Cc["@mozilla.org/extensions/manager;1"]
  112.                 .getService(Ci.nsIExtensionManager)
  113.                 .getItemForID(this.id);
  114.  
  115.   if(!updItem) {
  116.     return false;
  117.   }
  118.  
  119.   var appInfo = Cc["@mozilla.org/xre/app-info;1"]
  120.                 .getService(Ci.nsIXULAppInfo);
  121.  
  122.   this.name = updItem.name;
  123.   this.currentVersion = updItem.version;
  124.   GM_log("ExtensionUpdater: Got current item. name: " + updItem.name + ", " +
  125.          "version: " + updItem.version + ", " + "rdf: " + updItem.updateRDF);
  126.  
  127.   var prefs = Cc["@mozilla.org/preferences-service;1"]
  128.                 .getService(Ci.nsIPrefService)
  129.                 .getBranch("extensions.update.");
  130.   if (prefs.getPrefType("url") == prefs.PREF_INVALID) {
  131.     GM_log("ExtensionUpdater: Could not find updateURL in preferences");
  132.     return false;
  133.   }
  134.  
  135.   var xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
  136.                      .createInstance(Ci.nsIXULRuntime);
  137.  
  138.   this.updateURL = prefs.getComplexValue("url", Ci.nsIPrefLocalizedString).data;
  139.   this.updateURL = this.updateURL.replace(/%APP_ABI%/g, xulRuntime.XPCOMABI)
  140.                                  .replace(/%APP_ID%/g, appInfo.ID)
  141.                                  .replace(/%APP_OS%/g, xulRuntime.OS)
  142.                                  .replace(/%APP_VERSION%/g, appInfo.version)
  143.                                  .replace(/%ITEM_ID%/g, this.id)
  144.                                  .replace(/%ITEM_MAXAPPVERSION%/g,
  145.                                           updItem.maxAppVersion)
  146.                                  .replace(/%ITEM_STATUS%/g, "userEnabled")
  147.                                  .replace(/%ITEM_VERSION%/g,
  148.                                           this.currentVersion)
  149.                                  .replace(/%REQ_VERSION%/gi, "1");
  150.   GM_log("ExtensionUpdater: UpdateURL: " + this.updateURL);
  151.   return true;
  152. };
  153.  
  154. /**
  155.  * Connects to updateURL, retrieves and parses RDF, compares versions
  156.  * and calls installUpdate if required.
  157.  */
  158. ExtensionUpdater.prototype.attemptUpdate = function() {
  159.   GM_log("ExtensionUpdater: attemptUpdate");
  160.   if (!this.updateURL) {
  161.     return false;
  162.   }
  163.  
  164.   this.req_ = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
  165.                 .createInstance();
  166.   this.req_.onload = GM_hitch(this, "OnReqSuccess");
  167.   this.req_.onerror = GM_hitch(this, "OnReqFailure");
  168.   this.req_.open("GET", this.updateURL);
  169.   this.req_.send(null);
  170. }
  171.  
  172. ExtensionUpdater.prototype.OnReqFailure = function() {
  173.   this.failure("OnReqFailure");
  174. }
  175.  
  176. ExtensionUpdater.prototype.OnReqSuccess = function() {
  177.   GM_log("ExtensionUpdater: OnReqSuccess");
  178.  
  179.   // parseString (below) doesn't throw errors - rather they come from
  180.   // the RDF file itself, so we can't try/catch for invalid XML.
  181.   if (this.req_.status != 200) {
  182.     this.failure("ExtensionUpdater: Update URL request failed with status: " +
  183.                  this.req_.status);
  184.     return;
  185.   }
  186.  
  187.   if (!this.req_.responseText.match(/<rdf/gi)) {
  188.     this.failure("Error: Invalid Update RDF contents: " +
  189.                  this.req_.responseText);
  190.     return;
  191.   }
  192.  
  193.   var uri = Cc["@mozilla.org/network/io-service;1"]
  194.             .getService(Ci.nsIIOService)
  195.             .newURI(this.updateURL, null, null);
  196.   var parser = Cc["@mozilla.org/rdf/xml-parser;1"]
  197.                .createInstance(Ci.nsIRDFXMLParser);
  198.   var memoryDS = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
  199.                  .createInstance(Ci.nsIRDFDataSource);
  200.  
  201.   parser.parseString(memoryDS, uri, this.req_.responseText);
  202.  
  203.   GM_log("ExtensionUpdater: RDF loaded");
  204.  
  205.   var moz = "http://www.mozilla.org/2004/em-rdf#";
  206.  
  207.   var versionArc = this.RDFSvc_.GetResource(moz + "version");
  208.   var updateLinkArc = this.RDFSvc_.GetResource(moz + "updateLink");
  209.  
  210.   var thisResource = null;
  211.   var dsResources = memoryDS.GetAllResources();
  212.  
  213.   // Cycle through RDF looking for what we want what we want
  214.   GM_log("ExtensionUpdater: Cycling through RDF");
  215.   // TODO(glen): Make sure this matches the correct GUID for Firefox
  216.   //   also, check that update.rdf can't have some other funky format
  217.   while (dsResources.hasMoreElements()) {
  218.     thisResource = dsResources.getNext().QueryInterface(Ci.nsIRDFResource);
  219.  
  220.     var versionRes = memoryDS.GetTarget(thisResource, versionArc, true);
  221.  
  222.     if (versionRes) {
  223.       this.updateVersion = versionRes.QueryInterface(Ci.nsIRDFLiteral).Value;
  224.     }
  225.  
  226.     var updateLinkRes = memoryDS.GetTarget(thisResource, updateLinkArc, true);
  227.  
  228.     if (updateLinkRes) {
  229.       this.updateLink = updateLinkRes.QueryInterface(Ci.nsIRDFLiteral).Value;
  230.     }
  231.   }
  232.  
  233.   if (this.updateVersion && this.updateLink) {
  234.     GM_log("ExtensionUpdater: currentVersion: " + this.currentVersion + ", " +
  235.            "updateVersion: " + this.updateVersion + ", " +
  236.            "updateLink: " + this.updateLink);
  237.  
  238.     if (GM_compareVersions(this.updateVersion, this.currentVersion) == 1) {
  239.       GM_log("ExtensionUpdater: Local version is old, now installing " +
  240.              "update...");
  241.       this.installUpdate();
  242.     } else {
  243.       this.success("No need to update");
  244.     }
  245.   } else {
  246.     this.failure("No update info in rdf");
  247.   }
  248. }
  249.  
  250. /**
  251.  * Starts XPI retrieval and installation.
  252.  */
  253. ExtensionUpdater.prototype.installUpdate = function() {
  254.   if (!this.updateLink) {
  255.     this.failure("Failure");
  256.     return false;
  257.   }
  258.  
  259.   var manager = Cc["@mozilla.org/xpinstall/install-manager;1"]
  260.                  .createInstance(Ci.nsIXPInstallManager);
  261.  
  262.   if (manager != null) {
  263.     GM_log("ExtensionUpdater: installUpdate. link: " + this.updateLink);
  264.  
  265.     var items = [this.updateLink];
  266.  
  267.     // Figure out if extension should be updated (default to "yes")
  268.     var autoupdate = GM_prefRoot.getValue("enableUpdate",
  269.                                           AUTOUPDATE_DEFAULT_VALUE);
  270.     if (autoupdate == false) {
  271.       this.success("Would have updated, except update is disabled");
  272.     } else {
  273.       GM_log("ExtensionUpdater: Extension '" + this.name + "' updating...");
  274.       manager.initManagerFromChrome(items, items.length, this);
  275.     }
  276.   } else {
  277.     this.failure("Error creating manager");
  278.   }
  279. }
  280.  
  281. /**
  282.  * Part of observer for initManagerFromChrome
  283.  */
  284. ExtensionUpdater.prototype.onStateChange = function(index, state, value) {
  285.   if(state == Ci.nsIXPIProgressDialog.INSTALL_DONE) {
  286.     GM_log("ExtensionUpdater: onStateChange. Value: " + value);
  287.     if(value != 0) {
  288.       this.failure("Download Error");
  289.     } else {
  290.       this.success("Update installed");
  291.     }
  292.   }
  293. }
  294.  
  295. /**
  296.  * Part of observer for initManagerFromChrome
  297.  */
  298. ExtensionUpdater.prototype.onProgress = function(index, value, maxValue) {}
  299.  
  300. /**
  301.  * Success function
  302.  *
  303.  * @param {String} message
  304.  */
  305. ExtensionUpdater.prototype.success = function(aMessage) {
  306.   GM_log("ExtensionUpdater: success - " + aMessage);
  307.   this.updating = false;
  308. }
  309. /**
  310.  * Failure function
  311.  *
  312.  * @param {String} Error message
  313.  */
  314. ExtensionUpdater.prototype.failure = function(aMessage) {
  315.   GM_log("ExtensionUpdater: failed - " + aMessage);
  316.   this.updating = false;
  317. }
  318.